/*
* Purpose: Interrupt Rquest Controller driver/interface
*
* Author: Tristan Yang
*
* Date: 2007/09/21
*
* Copyright Generalplus Corp. ALL RIGHTS RESERVED.
*
* Version : 1.00
*/
#include "driver_l1.h"
#include "drv_l1_sfr.h"
#include "drv_l1_interrupt.h"

#define DEBUG_IRQ_CHAIN_INFINITE	0

// Interrupt IRQ mask register
#define C_VIC_IRQ_MASK_SET_ALL	0xFFFFFFFF

// Interrupt FIQ mask register
#define C_VIC_FIQ_MASK_SET_ALL	0x0000000F

// Interrupt Global mask register
#define C_VIC_GLOBAL_MASK_CLEAR 0x00000000
#define C_VIC_GLOBAL_MASK_SET	0x00000001

typedef struct
{	// Offset
	volatile INT32U INT_IRQFLAG_LOW;	// 0x0000
	volatile INT32U INT_FIQFLAG;		// 0x0004
	volatile INT32U INT_I_PMST;			// 0x0008
	volatile INT32U RESERVED_1;			// 0x000C
	volatile INT32U INT_I_PSLV0;		// 0x0010
	volatile INT32U INT_I_PSLV1;		// 0x0014
	volatile INT32U INT_I_PSLV2;		// 0x0018
	volatile INT32U INT_I_PSLV3;		// 0x001C
	volatile INT32U INT_KECON;			// 0x0020
	volatile INT32U INT_KEYCH_REG;		// 0x0024
	volatile INT32U INT_IRQNUM;			// 0x0028
	volatile INT32U INT_FIQNUM;			// 0x002C
	volatile INT32U INT_IRQMASK_LOW;	// 0x0030
	volatile INT32U INT_FIQMASK;		// 0x0034
	volatile INT32U INT_GMASK;			// 0x0038
	volatile INT32U RESERVED_2;			// 0x003C
	volatile INT32U INT_IRQFLAG_HIGH;	// 0x0040
	volatile INT32U INT_I_PSLV4;		// 0x0044
	volatile INT32U INT_I_PSLV5;		// 0x0048
	volatile INT32U INT_I_PSLV6;		// 0x004C
	volatile INT32U INT_I_PSLV7;		// 0x0050
	volatile INT32U INT_IRQMASK_HIGH;	// 0x0054
	volatile INT32U INT_FIQ_SRC_SEL;	// 0x0058 	
} INTERRUPT_SFR;

void (*irq_isr[VIC_MAX_IRQ]) (void);
void (*fiq_isr[VIC_MAX_FIQ]) (void);

/*****************************************************
* get_INTERRUPT_Reg_Base:
*
*****************************************************/
static INTERRUPT_SFR *get_INTERRUPT_Reg_Base(void)
{
	return (INTERRUPT_SFR *) P_INTERRUPT_BASE;
}

INT32U vic_global_mask_set(void)
{
	INT32S			old_mask;
	INTERRUPT_SFR	*pInterrupt_Reg;
  #if _OPERATING_SYSTEM != _OS_NONE
	OS_CPU_SR cpu_sr;
  #endif

	pInterrupt_Reg = get_INTERRUPT_Reg_Base();

#if _OPERATING_SYSTEM != _OS_NONE	// Soft Protect for critical section
	OS_ENTER_CRITICAL();
#endif
	old_mask = pInterrupt_Reg->INT_GMASK & C_VIC_GLOBAL_MASK_SET;
	pInterrupt_Reg->INT_GMASK = C_VIC_GLOBAL_MASK_SET;

#if _OPERATING_SYSTEM != _OS_NONE
	OS_EXIT_CRITICAL();
#endif
	return old_mask;
}

void vic_global_mask_restore(INT32U old_mask)
{
	INTERRUPT_SFR	*pInterrupt_Reg;
  #if _OPERATING_SYSTEM != _OS_NONE
	OS_CPU_SR cpu_sr;
  #endif

	pInterrupt_Reg = get_INTERRUPT_Reg_Base();

#if _OPERATING_SYSTEM != _OS_NONE	// Soft Protect for critical section
	OS_ENTER_CRITICAL();
#endif
	if(old_mask & C_VIC_GLOBAL_MASK_SET)
	{
		pInterrupt_Reg->INT_GMASK = C_VIC_GLOBAL_MASK_SET;
	}
	else
	{
		pInterrupt_Reg->INT_GMASK = C_VIC_GLOBAL_MASK_CLEAR;
	}

#if _OPERATING_SYSTEM != _OS_NONE
	OS_EXIT_CRITICAL();
#endif
}

INT32S vic_irq_register(INT32U irc_num, void (*isr) (void))
{
  #if _OPERATING_SYSTEM != _OS_NONE
	OS_CPU_SR cpu_sr;
  #endif
  
	if(irc_num && (irc_num < VIC_MAX_IRQ))
	{
		irc_num--;

#if _OPERATING_SYSTEM != _OS_NONE	// Soft Protect for critical section
		OS_ENTER_CRITICAL();
#endif
		irq_isr[irc_num] = isr;

#if _OPERATING_SYSTEM != _OS_NONE
		OS_EXIT_CRITICAL();
#endif
		return STATUS_OK;
	}

	return STATUS_FAIL;
}

INT32S vic_irq_unregister(INT32U irc_num)
{
  #if _OPERATING_SYSTEM != _OS_NONE
	OS_CPU_SR cpu_sr;
  #endif
  
	if(irc_num && (irc_num < VIC_MAX_IRQ))
	{
		irc_num--;

#if _OPERATING_SYSTEM != _OS_NONE	// Soft Protect for critical section
		OS_ENTER_CRITICAL();
#endif
		irq_isr[irc_num] = NULL;

#if _OPERATING_SYSTEM != _OS_NONE
		OS_EXIT_CRITICAL();
#endif
		return STATUS_OK;
	}

	return STATUS_FAIL;
}

INT32S vic_irq_enable(INT32U irc_num)
{
	INT32S			old_mask;
	INT32U			vic_boundary;
	INTERRUPT_SFR	*pInterrupt_Reg;
  #if _OPERATING_SYSTEM != _OS_NONE
	OS_CPU_SR cpu_sr;
  #endif

	pInterrupt_Reg = get_INTERRUPT_Reg_Base();

	if(irc_num && (irc_num < VIC_MAX_IRQ))
	{
#if _OPERATING_SYSTEM != _OS_NONE	// Soft Protect for critical section
		OS_ENTER_CRITICAL();
#endif
		vic_boundary = (VIC_MAX_IRQ >> 1);

		if(irc_num > vic_boundary)
		{
			irc_num = VIC_MAX_IRQ - irc_num - 1;
			old_mask = (pInterrupt_Reg->INT_IRQMASK_LOW >> irc_num) & 0x1;
			pInterrupt_Reg->INT_IRQMASK_LOW &= ~(1 << irc_num);
		}
		else
		{
			irc_num = (VIC_MAX_IRQ - vic_boundary) - irc_num - 1;
			old_mask = (pInterrupt_Reg->INT_IRQMASK_HIGH >> irc_num) & 0x1;
			pInterrupt_Reg->INT_IRQMASK_HIGH &= ~(1 << irc_num);
		}

#if _OPERATING_SYSTEM != _OS_NONE
		OS_EXIT_CRITICAL();
#endif
		return old_mask;
	}

	return STATUS_FAIL;
}

INT32S vic_irq_disable(INT32U irc_num)
{
	INT32S			old_mask;
	INT32U			vic_boundary;
	INTERRUPT_SFR	*pInterrupt_Reg;
  #if _OPERATING_SYSTEM != _OS_NONE
	OS_CPU_SR cpu_sr;
  #endif

	pInterrupt_Reg = get_INTERRUPT_Reg_Base();

	if(irc_num && (irc_num < VIC_MAX_IRQ))
	{
#if _OPERATING_SYSTEM != _OS_NONE	// Soft Protect for critical section
		OS_ENTER_CRITICAL();
#endif
		vic_boundary = (VIC_MAX_IRQ >> 1);

		if(irc_num > vic_boundary)
		{
			irc_num = VIC_MAX_IRQ - irc_num - 1;
			old_mask = (pInterrupt_Reg->INT_IRQMASK_LOW >> irc_num) & 0x1;
			pInterrupt_Reg->INT_IRQMASK_LOW |= (1 << irc_num);
		}
		else
		{
			irc_num = (VIC_MAX_IRQ - vic_boundary) - irc_num - 1;
			old_mask = (pInterrupt_Reg->INT_IRQMASK_HIGH >> irc_num) & 0x1;
			pInterrupt_Reg->INT_IRQMASK_HIGH |= (1 << irc_num);
		}

#if _OPERATING_SYSTEM != _OS_NONE
		OS_EXIT_CRITICAL();
#endif
		return old_mask;
	}

	return STATUS_FAIL;
}

INT32S vic_fiq_register(INT32U irc_num, void (*isr) (void))
{
  #if _OPERATING_SYSTEM != _OS_NONE
	OS_CPU_SR cpu_sr;
  #endif
  
	if(irc_num && (irc_num < VIC_MAX_FIQ))
	{
		irc_num--;

#if _OPERATING_SYSTEM != _OS_NONE	// Soft Protect for critical section
		OS_ENTER_CRITICAL();
#endif
		fiq_isr[irc_num] = isr;

#if _OPERATING_SYSTEM != _OS_NONE
		OS_EXIT_CRITICAL();
#endif
		return STATUS_OK;
	}

	return STATUS_FAIL;
}

INT32S vic_fiq_unregister(INT32U irc_num)
{
  #if _OPERATING_SYSTEM != _OS_NONE
	OS_CPU_SR cpu_sr;
  #endif
  
	if(irc_num && (irc_num < VIC_MAX_FIQ))
	{
		irc_num--;

#if _OPERATING_SYSTEM != _OS_NONE	// Soft Protect for critical section
		OS_ENTER_CRITICAL();
#endif
		fiq_isr[irc_num] = NULL;

#if _OPERATING_SYSTEM != _OS_NONE
		OS_EXIT_CRITICAL();
#endif
		return STATUS_OK;
	}

	return STATUS_FAIL;
}

INT32S vic_fiq_enable(INT32U irc_num)
{
	INT32S			old_mask;
	INTERRUPT_SFR	*pInterrupt_Reg;
  #if _OPERATING_SYSTEM != _OS_NONE
	OS_CPU_SR cpu_sr;
  #endif

	pInterrupt_Reg = get_INTERRUPT_Reg_Base();

	if(irc_num && (irc_num < VIC_MAX_FIQ))
	{
		irc_num = VIC_MAX_FIQ - irc_num - 1;

#if _OPERATING_SYSTEM != _OS_NONE	// Soft Protect for critical section
		OS_ENTER_CRITICAL();
#endif
		old_mask = (pInterrupt_Reg->INT_FIQMASK >> irc_num) & 0x1;
		pInterrupt_Reg->INT_FIQMASK &= ~(1 << irc_num);

#if _OPERATING_SYSTEM != _OS_NONE
		OS_EXIT_CRITICAL();
#endif
		return old_mask;
	}

	return STATUS_FAIL;
}

INT32S vic_fiq_disable(INT32U irc_num)
{
	INT32S			old_mask;
	INTERRUPT_SFR	*pInterrupt_Reg;
  #if _OPERATING_SYSTEM != _OS_NONE
	OS_CPU_SR cpu_sr;
  #endif

	pInterrupt_Reg = get_INTERRUPT_Reg_Base();

	if(irc_num && (irc_num < VIC_MAX_FIQ))
	{
		irc_num = VIC_MAX_FIQ - irc_num - 1;

#if _OPERATING_SYSTEM != _OS_NONE	// Soft Protect for critical section
		OS_ENTER_CRITICAL();
#endif
		old_mask = (pInterrupt_Reg->INT_FIQMASK >> irc_num) & 0x1;
		pInterrupt_Reg->INT_FIQMASK |= (1 << irc_num);

#if _OPERATING_SYSTEM != _OS_NONE
		OS_EXIT_CRITICAL();
#endif
		return old_mask;
	}

	return STATUS_FAIL;
}

#if DEBUG_IRQ_CHAIN_INFINITE
INT32U irq_cnt[VIC_MAX_IRQ];
INT32U irq_cont_cnt;
#endif

#if _OPERATING_SYSTEM != _OS_NONE
void irq_dispatcher (void)
#else
void IRQ irq_dispatcher (void)
#endif
{
	INT32U			vector;
	INTERRUPT_SFR	*pInterrupt_Reg;

	#if DEBUG_IRQ_CHAIN_INFINITE
	INT32U i;
	#endif

	pInterrupt_Reg = get_INTERRUPT_Reg_Base();

	if(pInterrupt_Reg->INT_GMASK & C_VIC_GLOBAL_MASK_SET)
	{			// Spurious interrupt. Do nothing.
		return;
	}

	vector = pInterrupt_Reg->INT_IRQNUM;
	while(vector)
	{
		if(vector < VIC_MAX_IRQ)
		{
			vector--;
			if(irq_isr[vector])
			{	// Check IRQ status register to see which interrupt source is set
				// TBD: Enable Device Protect and then disable Hard Protect here for nesting interrupt
				(*irq_isr[vector]) ();

				// TBD: Enable Hard Protect and then disable Device Protect here for nesting interrupt
			}
		}

		vector = pInterrupt_Reg->INT_IRQNUM;	// Get next pending interrupt source

		#if DEBUG_IRQ_CHAIN_INFINITE

		++irq_cont_cnt;
		++irq_cnt[vector];

		if(irq_cont_cnt >= 100)
		{
			DBG_PRINT("[ERROR] irq cont cnt = %d\r\n", irq_cont_cnt);
			for(i = 0 ; i < VIC_MAX_IRQ ; ++i)
			{
				if(irq_cnt[i])
				{
					DBG_PRINT("irq_cnt[%d]=%d\r\n", i, irq_cnt[i]);
				}
			}
			irq_cont_cnt = 0;
		}

		#endif
	}

	#if DEBUG_IRQ_CHAIN_INFINITE

	irq_cont_cnt = 0;

	for(i = 0 ; i < VIC_MAX_IRQ ; ++i)
	{
		irq_cnt[i] = 0;
	}

	#endif
}

#if _OPERATING_SYSTEM != _OS_NONE
void fiq_dispatcher (void)
#else
void IRQ fiq_dispatcher (void)
#endif
{
	INT32U			vector;
	INTERRUPT_SFR	*pInterrupt_Reg;

	pInterrupt_Reg = get_INTERRUPT_Reg_Base();

	if(pInterrupt_Reg->INT_GMASK & C_VIC_GLOBAL_MASK_SET)
	{			// Spurious interrupt. Do nothing.
		return;
	}

	vector = pInterrupt_Reg->INT_FIQNUM;

	while(vector)
	{
		if(vector < VIC_MAX_FIQ)
		{
			vector--;
			if(fiq_isr[vector])
			{	// Check FIQ status register to see which interrupt source is set
				// TBD: Enable Device Protect and then disable Hard Protect here for nesting interrupt
				(*fiq_isr[vector]) ();

				// TBD: Enable Hard Protect and then disable Device Protect here for nesting interrupt
			}
		}

		vector = pInterrupt_Reg->INT_FIQNUM;
	}
}

void vic_init(void)
{
	INT32U			i;
	INTERRUPT_SFR	*pInterrupt_Reg;

	pInterrupt_Reg = get_INTERRUPT_Reg_Base();

	pInterrupt_Reg->INT_IRQMASK_LOW = C_VIC_IRQ_MASK_SET_ALL;	// Mask all interrupt
	pInterrupt_Reg->INT_IRQMASK_HIGH = C_VIC_IRQ_MASK_SET_ALL;	// Mask all interrupt
	pInterrupt_Reg->INT_FIQMASK = C_VIC_FIQ_MASK_SET_ALL;		// Mask all fast interrupt
	pInterrupt_Reg->INT_GMASK = C_VIC_GLOBAL_MASK_CLEAR;		// Clear global mask
	for(i = 0; i < VIC_MAX_IRQ; i++)
	{
		irq_isr[i] = NULL;
	}

	for(i = 0; i < VIC_MAX_FIQ; i++)
	{
		fiq_isr[i] = NULL;
	}

#if _OPERATING_SYSTEM != _OS_NONE
	register_exception_table(EXCEPTION_IRQ, (INT32U) OS_CPU_IRQ_ISR);
	register_exception_table(EXCEPTION_FIQ, (INT32U) OS_CPU_FIQ_ISR);
#else
	register_exception_table(EXCEPTION_IRQ, (INT32U) irq_dispatcher);
	register_exception_table(EXCEPTION_FIQ, (INT32U) fiq_dispatcher);
#endif
}
